package com.gator.spring.framework.aop.support;

import com.gator.spring.framework.aop.aspect.PABLO_AfterReturningAdviceInterceptor;
import com.gator.spring.framework.aop.aspect.PABLO_AfterThrowingAdviceInterceptor;
import com.gator.spring.framework.aop.aspect.PABLO_MethodBeforeAdviceInterceptor;
import com.gator.spring.framework.aop.config.PABLO_AopConfig;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author PABLO
 * @Date 2022/5/8 13:34
 * @Desc 回调代理通知的扩展类，切点被执行时会先调用代理 AOP核心思想
 */
public class PABLO_AdvisedSupport {
    //被代理的对象的class  即目标类（切点）的class
    private Class<?> targetClass;
    //被代理的实例对象
    private Object target;
    //配置文件映射对象
    private PABLO_AopConfig config;
    //正则编译  用来匹配正在实例化的对象是否符合代理要求
    private Pattern pointCutClassPattern;
    //切点方法缓存，value保存该方法所有拦截器
    //transient修饰的变量不能序列化和反序列化
    private transient Map<Method, List<Object>> methodCache;

    public PABLO_AdvisedSupport(PABLO_AopConfig config) {
        this.config = config;
    }

    public Class<?> getTargetClass() {
        return this.targetClass;
    }

    public Object getTarget() {
        return this.target;
    }

    /**
     * @Description: 根据method获取切点方法的拦截器链
     * @Author: PABLO
     * @Date: 2022/5/9 12:46
     * @Params: [method, targetClass]
     * @Return: java.util.List<java.lang.Object>
     **/
    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) throws Exception {
        //每个切点方法在parse时都会被加载到缓存中，这里根据method获取该method的拦截器链
        //Object必须是一个intercept
        // --> 即PABLO_MethodInterceptor的实现类，如前置、后置、异常通知，最终调用每个通知的invoke
        List<Object> cached = methodCache.get(method);
        //缓存没有
        if (cached == null) {
            //根据参数类型列表和方法名称得到对应method
            Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());
            cached = methodCache.get(m);
            //底层逻辑，对代理方法进行一个兼容处理
            this.methodCache.put(m, cached);
        }
        return cached;
    }

    public void setTargetClass(Class<?> targetClass) {
        this.targetClass = targetClass;
        parse();
    }


    /**
     * @Description: 解析配置文件中的AOP设置项 生成拦截器链
     * @Author: PABLO
     * @Date: 2022/5/9 14:41
     * @Params: []
     * @Return: void
     **/
    private void parse() {
        String pointCut = config.getPointCut()
                .replaceAll("\\.", ".")
                .replaceAll("\\\\.\\*", ".*")
                .replaceAll("\\(", "\\\\(")
                .replaceAll("\\)", "\\\\)");
        //pointCut=public .* com.gator.spring.executor.service..*Service..*(.*)

        //玩正则
        String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 2);
        //class com.gator.spring.executor.service.impl.*
        pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(
                pointCutForClassRegex.lastIndexOf(" ") + 1));
        try {

            methodCache = new HashMap<Method, List<Object>>();
            Pattern pattern = Pattern.compile(pointCutForClassRegex);
            //com.gator.spring.executor.aspect.LogAspect
            Class aspectClass = Class.forName(this.config.getAspectClass());
            Map<String, Method> aspectMethods = new HashMap<String, Method>();
            //将切面类中所有通知方法存入集合中，在下面匹配切点，匹配上的存入 对应 method的 拦截器链
            for (Method m : aspectClass.getDeclaredMethods()) {
                //切面方法，即每个通知
                aspectMethods.put(m.getName(), m);
            }
            //获取目标类方法,但不包括继承的方法
            for (Method m : this.targetClass.getDeclaredMethods()) {
                String methodString = m.toString();
                //处理方法上的异常
                if (methodString.contains("throws")) {
                    //截取目标的方法名称
                    methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim();
                }
                //【核心】正则匹配方法名，如匹配，构建执行器链，即将目标方法包装为拦截器！！！！！！！！！
                Matcher matcher = pattern.matcher(methodString);
                if (matcher.matches()) {
                    //执行器链
                    List<Object> advices = new LinkedList<Object>();
                    //把每一个方法包装成 MethodIterceptor拦截器链的节点
                    //before
                    if (!(null == config.getAspectBefore() || "".equals(config.getAspectBefore()))) {
                        //创建前置通知对象
                        advices.add(new PABLO_MethodBeforeAdviceInterceptor(aspectMethods.get(config.getAspectBefore()), aspectClass.newInstance()));
                    }
                    //after
                    if (!(null == config.getAspectAfter() || "".equals(config.getAspectAfter()))) {
                        //创建后置通知对象
                        advices.add(new PABLO_AfterReturningAdviceInterceptor(aspectMethods.get(config.getAspectAfter()), aspectClass.newInstance()));
                    }
                    //afterThrowing
                    if (!(null == config.getAspectAfterThrow() || "".equals(config.getAspectAfterThrow()))) {
                        //创建一个Advivce
                        PABLO_AfterThrowingAdviceInterceptor throwingAdvice =
                                new PABLO_AfterThrowingAdviceInterceptor(
                                        aspectMethods.get(config.getAspectAfterThrow()),
                                        aspectClass.newInstance());
                        throwingAdvice.setThrowName(config.getAspectAfterThrowingName());
                        advices.add(throwingAdvice);
                    }
                    methodCache.put(m, advices);
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public boolean pointCutMatch() {
        return pointCutClassPattern.matcher(this.targetClass.toString()).matches();
    }
}
